#include <stdlib.h>

#include <common/macro/build.h>
#include <common/macro/debug.h>
#include <common/linkedlist.h>

/** 
* @brief Create new empty linked list.
* 
* @return 
*/
API_LIBLOCAL inline linked_list_t *linked_list_new() {
	linked_list_t *list;

	list = malloc(sizeof(*list));

	list->head = NULL;
	list->tail = NULL;

	return list;
}

/** 
* @brief Deallocate memory for the linked list. Does not destroy possible
* items in it.
* 
* @param list
*/
API_LIBLOCAL inline void linked_list_destroy(linked_list_t *list) {
	if (list == NULL) return;
	free(list);
}

/** 
* @brief Returns first item in the linked list.
* 
* @param list
* 
* @return 
*/
API_LIBLOCAL inline linked_list_item_t *linked_list_head(linked_list_t *list) {
	assert(list != NULL);
	return list->head;
}

/** 
* @brief Returns last item in the linked list.
* 
* @param list
* 
* @return 
*/
API_LIBLOCAL inline linked_list_item_t *linked_list_tail(linked_list_t *list) {
	assert(list != NULL);
	return list->tail;
}

/** 
* @brief Appends a given item after a reference.
* 
* @param list
* @param item Reference item.
* @param newitem Appended item.
*/
API_LIBLOCAL inline void linked_list_link_after(linked_list_t *list, linked_list_item_t *item, linked_list_item_t *newitem) {
	assert(list != NULL);
	assert(newitem != NULL);

	if (item == NULL) {
		assert(list->head == NULL && list->tail == NULL);

		list->head = newitem;
		list->tail = newitem;
		newitem->prev = NULL;
		newitem->next = NULL;
	} else {
		newitem->next = item->next;
		newitem->prev = item;
		item->next = newitem;
	}

	if (list->tail == item) {
		list->tail = newitem;
	}
}

/** 
* @brief Appends a new value after a reference.
* 
* @param list
* @param item Reference item.
* @param value Appended value.
*/
API_LIBLOCAL inline void linked_list_insert_after(linked_list_t *list, linked_list_item_t *item, void *value) {
	linked_list_item_t *newitem;

	assert(list != NULL);

	newitem = malloc(sizeof(*newitem));
	newitem->data = value;

	linked_list_link_after(list, item, newitem);
}

/** 
* @brief Prepends a given item before a reference.
* 
* @param list
* @param item Reference item.
* @param newitem Prepended item.
*/
API_LIBLOCAL inline void linked_list_link_before(linked_list_t *list, linked_list_item_t *item, linked_list_item_t *newitem) {
	assert(list != NULL);
	assert(newitem != NULL);

	if (item == NULL) {
		assert(list->head == NULL && list->tail == NULL);

		list->head = newitem;
		list->tail = newitem;
		newitem->prev = NULL;
		newitem->next = NULL;
	} else {
		newitem->next = item;
		newitem->prev = item->prev;
		item->prev = newitem;
	}

	if (list->head == item) {
		list->head = newitem;
	}
}

/** 
* @brief Prepends a new value before a reference.
* 
* @param list
* @param item Reference item.
* @param value Prepended value.
*/
API_LIBLOCAL inline void linked_list_insert_before(linked_list_t *list, linked_list_item_t *item, void *value) {
	linked_list_item_t *newitem;

	assert(list != NULL);

	newitem = malloc(sizeof(*newitem));
	newitem->data = value;

	linked_list_link_before(list, item, newitem);
}

/** 
* @brief Deletes an item from the linked list.
* 
* @param list
* @param item
*/
API_LIBLOCAL inline void linked_list_delete(linked_list_t *list, linked_list_item_t *item) {
	assert(list != NULL);
	assert(item != NULL);

	linked_list_unlink(list, item);
	
	free(item);
}

/** 
* @brief Unlinks a linked list item from the list.
* 
* @param list
* @param item
*/
API_LIBLOCAL inline void linked_list_unlink(linked_list_t *list, linked_list_item_t *item) {
	assert(list != NULL);
	assert(item != NULL);

	/*
	 * It is implicitely assumed the item is in the list. 
	 */

	if (item == list->head)
		list->head = item->next;
	if (item == list->tail)
		list->tail = item->prev;

	if (item->next != NULL)
		item->next->prev = item->prev;
	if (item->prev != NULL)
		item->prev->next = item->next;
}


/** 
* @brief Returns the value of the linked list item.
* 
* @param item
* 
* @return 
*/
API_LIBLOCAL inline void *linked_list_item_value(linked_list_item_t *item) {
	assert(item != NULL);

	return item->data;
}
